/*
 * @(#) $Header$
 *
 * Copyright (C) 2007  Forklabs Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.media.jai.operator;

import java.awt.RenderingHints;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderableImage;
import java.util.Collection;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.ParameterListDescriptor;
import javax.media.jai.RenderableOp;
import javax.media.jai.RenderedOp;
import javax.media.jai.registry.CollectionRegistryMode;
import javax.media.jai.registry.RenderableCollectionRegistryMode;
import javax.media.jai.registry.RenderableRegistryMode;
import javax.media.jai.registry.RenderedRegistryMode;
import ca.forklabs.media.jai.CollectionDescriptor;

/**
 * Class {@code PipelineDescriptor} is an {@link OperationDescriptor} describing
 * the <em>pipeline</em> operation. This operator operates in four modes,
 * rendered, renderable, collection and renderable collection.
 * <p>
 * The <em>pipeline</em> operation takes, optionally a source image, a list of
 * operations and a list of parameter blocks. The two lists must be parallel,
 * that is the first parameter block is applied to the first operation, the
 * second parameter block is applied to the second operation and so on.
 * <p>
 * The source image is not required if the first operation creates an image,
 * examples of such operations are core operations <em>constant</em> and
 * <em>fileload</em>. If this assumption is not respected, there will be errors
 * during the processing.
 * <p>
 * In the collection modes, the source image can either be a collection of
 * images or multiple source image. All the source images will be collated one
 * after the other into one collection.
 * <p>
 * The <em>pipeline</em> operation executes each operation, one after the other.
 * The source image of the first operation is the given source image while the
 * source image of the other operations is the sink image of its previous
 * operation.
 *
 * <table border=1>
 * <caption>Resource List</caption>
 * <tr><th>Name</th>        <th>Value</th></tr>
 * <tr><td>GlobalName</td>  <td>Pipeline</td></tr>
 * <tr><td>LocalName</td>   <td>Pipeline</td></tr>
 * <tr><td>Vendor</td>      <td>ca.forklabs.media.jai.opimage</td></tr>
 * <tr><td>Description</td> <td>Multiple JAI operations seen as one</td></tr>
 * <tr><td>DocURL</td>      <td>n/a</td></tr>
 * <tr><td>Version</td>     <td>$Version$</td></tr>
 * <tr><td>Arg0Desct</td>   <td>The list of operations</td></tr>
 * <tr><td>Arg1Desct</td>   <td>The list of parameter blocks</td></tr>
 * </table>
 *
 * <table border=1>
 * <caption>Parameter List</caption>
 * <tr><th>Name</th>         <th>Class Type</th>                 <th>Default Value</th></tr>
 * <tr><td>operations</td>   <td>{@link String}[]</td>           <td>new String[0]</td>
 * <tr><td>parameters</td>   <td>{@link ParameterBlock}[]</td>   <td>new ParameterBlock[0]</td>
 * </table>
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.media.jai.operator.PipelineDescriptor">Daniel Léonard</a>
 * @version $Revision$
 */
public class PipelineDescriptor extends CollectionDescriptor {

//---------------------------
// Class variables
//---------------------------

   /** <em>serialVersionUID</em>. */
   private static final long serialVersionUID = -837451506676310447L;

   /** The name of this operator. */
   public static final String NAME = "Pipeline"; //$NON-NLS-1$

   /** The name of the operations parameter. */
   public static final String OPERATIONS_PARAMETER_NAME = "operations"; //$NON-NLS-1$
   /** The name of the parameters parameter. */
   public static final String PARAMETERS_PARAMETER_NAME = "parameters"; //$NON-NLS-1$

   /** The position of the operations parameter. */
   public static final int OPERATIONS_PARAMETER_INDEX = 0;
   /** The position of the parameters parameter. */
   public static final int PARAMETERS_PARAMETER_INDEX = 1;

   /**
    * The resource strings that provide the general documentation and specify
    * the parameter list for this operation.
    */
   private static final String[][] RESOURCES =
   {
      { "GlobalName",  PipelineDescriptor.NAME, }, //$NON-NLS-1$
      { "LocalName",   PipelineDescriptor.NAME, }, //$NON-NLS-1$
      { "Vendor",      "ca.forklabs.media.jai.opimage", }, //$NON-NLS-1$ //$NON-NLS-2$
      { "Description", PipelineDescriptor.getDescription(), }, //$NON-NLS-1$
      { "DocURL",      "n/a", }, //$NON-NLS-1$ //$NON-NLS-2$
      { "Version",     "$Version$", }, //$NON-NLS-1$ //$NON-NLS-2$
      { "arg0Desc",    PipelineDescriptor.getArg0Description(), }, //$NON-NLS-1$
      { "arg1Desc",    PipelineDescriptor.getArg1Description(), }, //$NON-NLS-1$
   };

   /** The supported modes. */
   private static final String[] SUPPORTED_MODES =
   {
      RenderedRegistryMode.MODE_NAME,
      RenderableRegistryMode.MODE_NAME,
      CollectionRegistryMode.MODE_NAME,
      RenderableCollectionRegistryMode.MODE_NAME,
   };

   /** The name of the source, use default. */
   private static final String[] SOURCE_NAMES = null;

   /** The type of source for each mode. */
   private static final Class<?>[][] SOURCE_CLASSES = new Class<?>[][]
   {
      { Collection.class, },
      { Collection.class, },
      { Collection.class, },
      { Collection.class, },
   };

   /** The parameter list descriptor for all modes. */
   private static final ParameterListDescriptor PARAMETER_LIST_DESCRIPTOR = new CollectionDescriptor.EmptyParameterListDescriptor() {

      @Override
      public int getNumParameters() {
         int num_parameters = 2;
         return num_parameters;
         }

      @Override
      public Class<?>[] getParamClasses() {
         Class<?>[] clazzes = new Class<?>[] {
            String[].class,
            ParameterBlock[].class,
            };
         return clazzes;
         }

      @Override
      public Object getParamDefaultValue(String name) {
         int index;
         if (PipelineDescriptor.OPERATIONS_PARAMETER_NAME.equals(name)) {
            index = PipelineDescriptor.OPERATIONS_PARAMETER_INDEX;
            }
         else if (PipelineDescriptor.PARAMETERS_PARAMETER_NAME.equals(name)) {
            index = PipelineDescriptor.PARAMETERS_PARAMETER_INDEX;
            }
         else {
         // this call throws an exception
            return super.getParamDefaultValue(name);
            }

         Object[] values = this.getParamDefaults();
         Object value = values[index];
         return value;
         }

      @Override
      public Object[] getParamDefaults() {
      // defaults would be empty arrays, that way the
      // operator does nothing
         Object[] defaults = new Object[] {
            new String[] { },
            new ParameterBlock[] { },
            };
         return defaults;
         }

      @Override
      public String[] getParamNames() {
         String[] names = {
            PipelineDescriptor.OPERATIONS_PARAMETER_NAME,
            PipelineDescriptor.PARAMETERS_PARAMETER_NAME,
            };
         return names;
         }

      @Override
      public boolean isParameterValueValid(String name, Object value) {
      // each parameter is an array, check that it is not null
      // and nothing more
         boolean is_valid = (null != value);
         return is_valid;
         }

      };

   /** Description of the parameters. */
   private static final ParameterListDescriptor[] PARAMETER_LIST_DESCRIPTORS = new ParameterListDescriptor[]
   {
      PipelineDescriptor.PARAMETER_LIST_DESCRIPTOR,
      PipelineDescriptor.PARAMETER_LIST_DESCRIPTOR,
      PipelineDescriptor.PARAMETER_LIST_DESCRIPTOR,
      PipelineDescriptor.PARAMETER_LIST_DESCRIPTOR,
   };


//---------------------------
// Constructor
//---------------------------

   /**
    * Constructor.
    */
   public PipelineDescriptor() {
      super(RESOURCES, SUPPORTED_MODES, SOURCE_NAMES, SOURCE_CLASSES, PARAMETER_LIST_DESCRIPTORS);
      }


//---------------------------
// Instance methods
//---------------------------

   /**
    * Validates that the operator and parameter block lists have the same size.
    * @param   pb   the parameter block holding the lists.
    * @param   sb   string buffer for error messages.
    * @return   {@code true} if all is well, {@code false} otherwise.
    */
   protected boolean validateParallelism(ParameterBlock pb, StringBuffer sb) {
      String[] operations = (String[]) pb.getObjectParameter(PipelineDescriptor.OPERATIONS_PARAMETER_INDEX);
      ParameterBlock[] parameters = (ParameterBlock[]) pb.getObjectParameter(PipelineDescriptor.PARAMETERS_PARAMETER_INDEX);

      int o_len = operations.length;
      int p_len = parameters.length;

      boolean is_ok = (o_len == p_len);
      if (false == is_ok) {
         String message = this.getBadAlignmentErrorMessage(o_len, p_len);
         sb.append(message);
         }

      return is_ok;
      }


//---------------------------
// Overridden methods from javax.media.jai.OperationDescriptorImpl
//---------------------------

   /**
    * Validates the input parameters, that is that there is the same number of
    * operations and parameter blocks.
    * @param   mode   the rendering mode.
    * @param   pb   the parameter block.
    * @param   sb   string buffer for error messages.
    * @return   {@code true} if all is well, {@code false} otherwise.
    */
   @Override
   protected boolean validateParameters(String mode, ParameterBlock pb, StringBuffer sb) {
      boolean is_ok = true;
      is_ok = is_ok && super.validateParameters(mode, pb, sb);
      is_ok = is_ok && this.validateParallelism(pb, sb);
      return is_ok;
      }

   /**
    * This operation is in immediate mode.
    * @return   always {@code true}.
    */
   @Override
   public boolean isImmediate() {
      return true;
      }


//---------------------------
// Class methods
//---------------------------

   /**
    * Creates and fills a new parameter block.
    * @param   mode   the rendering mode.
    * @param   source   the source image.
    * @param   operations  the list of operations.
    * @param   parameters   the list of parameter blocks.
    * @return   a new parameter block.
    */
   public static ParameterBlockJAI createParameterBlock(String mode, Object source, String[] operations, ParameterBlock[] parameters) {
      String name = PipelineDescriptor.NAME;
      ParameterBlockJAI pb = new ParameterBlockJAI(name, mode);
      if (null != source) {
         pb.addSource(source);
         }
      pb.setParameter(OPERATIONS_PARAMETER_NAME, operations);
      pb.setParameter(PARAMETERS_PARAMETER_NAME, parameters);
      return pb;
      }

   /**
    * Performs the list of operation in rendered mode.
    * @param   source   the initial source image, can be {@code null} if the
    *                   first operation produces an image from its parameter
    *                   block.
    * @param   operations   the list of operations.
    * @param   parameters   the list of parameter blocks.
    * @param   hints    the rendering hints, may be {@code null}.
    * @return   the rendered result image.
    */
   public static RenderedOp create(RenderedImage source, String[] operations, ParameterBlock[] parameters, RenderingHints hints) {
      String name = PipelineDescriptor.NAME;
      String mode = RenderedRegistryMode.MODE_NAME;
      ParameterBlockJAI pb = PipelineDescriptor.createParameterBlock(mode, source, operations, parameters);
      RenderedOp image = JAI.create(name, pb, hints);
      return image;
      }

   /**
    * Performs the list of operation in renderable mode.
    * @param   source   the initial source image, can be {@code null} if the
    *                   first operation produces an image from its parameter
    *                   block.
    * @param   operations   the list of operations.
    * @param   parameters   the list of parameter blocks.
    * @param   hints    the rendering hints, may be {@code null}.
    * @return   the rendered result image.
    */
   public static RenderableOp createRenderable(RenderableImage source, String[] operations, ParameterBlock[] parameters, RenderingHints hints) {
      String name = PipelineDescriptor.NAME;
      String mode = RenderableRegistryMode.MODE_NAME;
      ParameterBlockJAI pb = PipelineDescriptor.createParameterBlock(mode, source, operations, parameters);
      RenderableOp image = JAI.createRenderable(name, pb, hints);
      return image;
      }

   /**
    * Performs the list of operation in collection mode.
    * @param   sources   the source images, can be {@code null} if the first
    *                    operation produces images from its parameter block.
    * @param   operations   the list of operations.
    * @param   parameters   the list of parameter blocks.
    * @param   hints    the rendering hints, may be {@code null}.
    * @return   the rendered result image.
    */
   @SuppressWarnings("unchecked")
   public static Collection<RenderedImage> createCollection(Collection<RenderedImage> sources, String[] operations, ParameterBlock[] parameters, RenderingHints hints)  {
      String name = PipelineDescriptor.NAME;
      String mode = CollectionRegistryMode.MODE_NAME;
      ParameterBlockJAI pb = PipelineDescriptor.createParameterBlock(mode, sources, operations, parameters);
      Collection<RenderedImage> sinks = JAI.createCollection(name, pb, hints);
      return sinks;
      }

   /**
    * Performs the list of operation in renderable collection mode.
    * @param   sources   the source images, can be {@code null} if the first
    *                    operation produces images from its parameter block.
    * @param   operations   the list of operations.
    * @param   parameters   the list of parameter blocks.
    * @param   hints    the rendering hints, may be {@code null}.
    * @return   the rendered result image.
    */
   @SuppressWarnings("unchecked")
   public static Collection<RenderableImage> createRenderableCollection(Collection<RenderableImage> sources, String[] operations, ParameterBlock[] parameters, RenderingHints hints)  {
      String name = PipelineDescriptor.NAME;
      String mode = RenderableCollectionRegistryMode.MODE_NAME;
      ParameterBlockJAI parameter_block = PipelineDescriptor.createParameterBlock(mode, sources, operations, parameters);
      Collection<RenderableImage> sinks = JAI.createRenderableCollection(name, parameter_block, hints);
      return sinks;
      }


//---------------------------
// External resources methods
//---------------------------

   /**
    * Gets the description of this operation.
    * @return   the description message.
    */
   protected static String getDescription() {
      String key = Resources.PIPELINE_DESCRIPTION;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the description for the first argument, the list of operations.
    * @return   the description message.
    */
   protected static String getArg0Description() {
      String key = Resources.PIPELINE_ARG0_DESCRIPTION;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the description for the second argument, the list of parameter
    * blocks.
    * @return   the description message.
    */
   protected static String getArg1Description() {
      String key = Resources.PIPELINE_ARG1_DESCRIPTION;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the error message saying that there are not the same number of
    * operations and parameter blocks.
    * @param o_len   the number of operations.
    * @param p_len   the number of parameter blocks.
    * @return   the error message.
    */
   @SuppressWarnings("boxing")
   protected String getBadAlignmentErrorMessage(int o_len, int p_len) {
      String key = Resources.PIPELINE_BAD_ALIGNEMENT;
      String message = Resources.getLocalizedString(key, o_len, p_len);
      return message;
      }

   }
